﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace Xceed.Silverlight.Data
{
  internal class StandardDataSourceEnumerator : DataSourceEnumeratorBase, ICachingEnumerator
  {
    #region Static Fields

    private const int BufferSizeRatio = 6;

    private static readonly Guid JumpMoveAndFetchCallId = DataUtils.CreateCallId();

    #endregion

    #region Constructor

    internal StandardDataSourceEnumerator(
      ReactiveCacheEnumerator childEnumerator,
      ReactiveDataPathEnumerator reactiveEnumerator,
      IEnumerable<GroupCacheEnumeratorBase> groupCacheEnumerators,
      IGroupDataHelper dataSourceHelper,
      GroupHandler groupHandler )
      : base( childEnumerator, groupCacheEnumerators, reactiveEnumerator, dataSourceHelper, groupHandler )
    {
      this.ReactiveEnumerator = reactiveEnumerator;

      childEnumerator.OperationCompleted += new EventHandler<OperationCompletedEventArgs<DataPath>>( this.OnOperationCompleted );
    }

    #endregion

    #region CacheSize Property

    public int CacheSize
    {
      get
      {
        return this.CacheEnumerator.CacheSize;
      }
    }

    #endregion

    #region CacheEnumerator Property

    private ReactiveCacheEnumerator CacheEnumerator
    {
      get
      {
        return ( ReactiveCacheEnumerator )this.ChildEnumeratorTyped;
      }
    }

    #endregion

    #region ReactiveEnumerator Property

    private ReactiveDataPathEnumerator ReactiveEnumerator
    {
      get;
      set;
    }

    #endregion

    public override SourcePosition CreateGroupFetchSourcePosition( DataPath groupPath, StartPosition fetchStartPosition )
    {
      if( !this.ReactiveEnumerator.DataItemFactory.IsDummy( groupPath ) )
        return base.CreateGroupFetchSourcePosition( groupPath, fetchStartPosition );

      int groupDepth = groupPath.Depth;

      Func<DataPath, SourcePosition> sortPositionCreator =
        ( path ) =>
        {
          if( path.Depth < groupDepth )
            throw new DataInternalException();

          if( path.Depth > groupDepth )
          {
            path = path.CreateAncestorPath( groupDepth );
          }

          return base.CreateGroupFetchSourcePosition( path, fetchStartPosition );
        };

      DependentPosition<DataPath> dependentPosition =
        new DependentPosition<DataPath>( groupPath, sortPositionCreator );

      return dependentPosition;
    }

    protected override void RaiseHasPendingRequestsChanged()
    {
      if( !this.ReactiveEnumerator.HasPendingRequests )
      {
        this.AdjustIndexRelativePosition();
      }

      base.RaiseHasPendingRequestsChanged();
    }

    protected override void AdjustIndexRelativePosition()
    {
      if( !this.HasPendingRequests )
      {
        base.AdjustIndexRelativePosition();
      }
    }

    protected override void HandleInconsistentCountData( ref RelativeOffset currentOffset, ref EstimateItemCount itemCount )
    {
      Debug.Assert( false, "There is inconsistency in the counts. Either the request is faulty, or the cache assume invalid data." );
      currentOffset = currentOffset.ToEstimate();
    }

    private int FindDataPath( DataPath path, IEnumerable<DataPath> collection )
    {
      if( collection != null )
      {
        IList<DataPath> list = collection as IList<DataPath>;

        if( list != null )
          return list.IndexOf( path );

        int indexOf = 0;

        foreach( DataPath item in collection )
        {
          if( object.Equals( item, path ) )
            return indexOf;

          indexOf++;
        }
      }

      return -1;
    }

    private void OnOperationCompleted( object sender, OperationCompletedEventArgs<DataPath> e )
    {
      if( e.Action == OperationCompletedAction.Items )
      {
        this.OnItemsOperationCompleted( e );
      }
      else
      {
        this.OnStatResultOperationCompleted( e );
      }
    }

    private void OnStatResultOperationCompleted( OperationCompletedEventArgs<DataPath> e )
    {
      this.RaiseDataChanged( new DataChangedEventArgs( DataChangedAction.StatLoaded, e.StatParameters, e.RealStatResults, e.DummyStatResults ) );
    }

    private void OnItemsOperationCompleted( OperationCompletedEventArgs<DataPath> e )
    {

      RelativeOffset currentOffset = null;
      DataPath removedCurrent = null;
      bool updateItems = false;
      List<Tuple<object, object>> itemsLoaded = new List<Tuple<object, object>>();

      if( e.Replacements != null )
      {
        foreach( Tuple<DataPath, DataPath> replacement in e.Replacements )
        {
          this.HandleCurrentReplace( replacement.Item1, replacement.Item2 );

          if( this.CacheEnumerator.CachedItems.Contains( replacement.Item2 ) )
          {
            itemsLoaded.Add( new Tuple<object, object>( replacement.Item1, replacement.Item2 ) );
          }
        }
      }

      if( e.Deletions != null )
      {
        updateItems = updateItems || ( e.Deletions.Count > 0 );
        SourcePosition notUsed;
        foreach( DataPath deletedPath in e.Deletions )
        {
          if( this.HandleCurrentRemove( deletedPath, e.Operation.EndPositionObject, out notUsed, out currentOffset ) )
          {
            removedCurrent = deletedPath;
            break;
          }
        }
      }

      if( this.CurrentPosition is ItemPosition )
      {
        CacheEnumerator cache = this.CacheEnumerator;
        DataPath path = ( DataPath )( ( ItemPosition )this.CurrentPosition ).Item;

        RelativeOffset endPositionOffset = this.HandleUnsupportedEstimateIndex( e.Operation.EndPositionOffset );
        if( endPositionOffset != null )
        {
          int pathOffset = this.FindDataPath( path, e.Operation.FetchedItems );

          if( pathOffset >= 0 )
          {
            long referenceOffset = ( pathOffset * e.Operation.GetFetchUnit() ) - e.Operation.GetFetchDisplacement();

            currentOffset = DataUtils.CalculateRelativeOffset(
              endPositionOffset,
              referenceOffset );
          }
        }

        if( currentOffset == null )
        {
          RelativeOffset cacheFirstItemOffset = this.HandleUnsupportedEstimateIndex( cache.FirstItemOffset );
          if( cacheFirstItemOffset != null )
          {
            int pathOffset = this.FindDataPath( path, cache.CachedItems );

            if( pathOffset >= 0 )
            {
              currentOffset = DataUtils.CalculateRelativeOffset(
                cacheFirstItemOffset,
                pathOffset );
            }
          }
        }
      }

      EstimateItemCount newCount = null;
      if( e.Operation.TotalCount != null )
      {
        newCount = new EstimateItemCount( e.Operation.TotalCount, false );
      }

      if( currentOffset != null && newCount == null && this.ItemCountEstimate.IsBasedOnOffset )
      {
        newCount = this.ItemCountEstimate;
        newCount = DataSourceEnumeratorBase.CalculateNewCountBasedOnOffsets( this.CurrentOffset, currentOffset, newCount );
      }

      this.SetCountValues( currentOffset, newCount );

      if( removedCurrent != null )
      {
        this.RaiseDataRemoved( removedCurrent, e.Operation.EndPositionObject, e.Operation.EndPositionObject );
      }
      else if( itemsLoaded.Count > 0 || updateItems )
      {
        // Rise ItemsLoaded events.
        this.RaiseDataChanged( new DataChangedEventArgs( DataChangedAction.ItemsLoaded, itemsLoaded, updateItems ) );
      }
    }


    #region SetCacheSize Methods

    public void SetCacheSize( int size )
    {
      this.CacheEnumerator.SetCacheSize( size, this.CurrentPosition );
    }

    public void SetCacheSize( int size, double topTrimRatio )
    {
      this.CacheEnumerator.SetCacheSize( size, topTrimRatio );
    }

    public void SetCacheSize( int size, SourcePosition topTrimLimit )
    {
      this.CacheEnumerator.SetCacheSize( size, topTrimLimit );
    }

    #endregion

    #region BeginJumpMoveAndFetch Methods

    public override JumpMoveAndFetchAsyncResult BeginJumpMoveAndFetch( JumpMoveAndFetchParameters parameters, AsyncCallback userCallback, object asyncState )
    {
      this.CheckMoveAndFetchParameters( parameters.ExpectedMoveCount, parameters.ExpectedFetchCount );

      JumpMoveAndFetchAsyncResult asyncResult = new JumpMoveAndFetchAsyncResult( StandardDataSourceEnumerator.JumpMoveAndFetchCallId, userCallback, asyncState, parameters );

      //If a move-only operation is made, always get the last item anyway in 
      //order to always have the "current position" in cache.
      if( parameters.ExpectedFetchCount == 0 )
      {
        parameters = parameters.CreateWithNewFetchCount(
                       DataUtils.GetUnit( parameters.ExpectedMoveCount ) );
      }

      asyncResult.Start(
        ( callback, state ) => this.BaseBeginJumpMoveAndFetch( parameters, callback, state ),
        null );

      return asyncResult;
    }

    public override JumpMoveAndFetchResult<DataPath> EndJumpMoveAndFetch( JumpMoveAndFetchAsyncResult jumpMoveAndFetchAsyncResult )
    {
      jumpMoveAndFetchAsyncResult.ValidateCallEnd( StandardDataSourceEnumerator.JumpMoveAndFetchCallId );

      JumpMoveAndFetchResult<DataPath> result = base.EndJumpMoveAndFetch( jumpMoveAndFetchAsyncResult.GetLastAsyncResult<JumpMoveAndFetchAsyncResult>() );

      //Remove the "extra fetch" if the expected fetch count was zero.
      //See ReactiveWithCacheEnumerator.BeginJumpMoveAndFetch)
      if( ( jumpMoveAndFetchAsyncResult.ExpectedFetchCount == 0 )
        && ( result.FetchedItems != null && result.FetchedItems.Count > 0 ) )
      {
        result = new JumpMoveAndFetchResult<DataPath>(
                       jumpMoveAndFetchAsyncResult,
                       result.ActualMoveCount,
                       new List<DataPath>(),
                       result.Result,
                       result.EndPositionObject,
                       result.EndPositionOffset,
                       result.TotalCount );
      }
      else
      {
        result = new JumpMoveAndFetchResult<DataPath>(
                       jumpMoveAndFetchAsyncResult,
                       result.ActualMoveCount,
                       result.FetchedItems,
                       result.Result,
                       result.EndPositionObject,
                       result.EndPositionOffset,
                       result.TotalCount );
      }
      return result;
    }

    private IAsyncResult BaseBeginJumpMoveAndFetch( JumpMoveAndFetchParameters parameters, AsyncCallback userCallback, object asyncState )
    {
      return base.BeginJumpMoveAndFetch( parameters, userCallback, asyncState );
    }

    #endregion

    #region IDisposable Members

    private bool m_disposed; //false

    protected override void Dispose( bool disposing )
    {
      if( !m_disposed )
      {
        this.CacheEnumerator.OperationCompleted -= new EventHandler<OperationCompletedEventArgs<DataPath>>( OnOperationCompleted );

        m_disposed = true;

        base.Dispose( disposing );
      }
    }

    #endregion
  }
}
